/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.admin.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.AllArgsConstructor;
import org.springblade.common.cache.CacheNames;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.Func;
import org.springblade.modules.admin.entity.*;
import org.springblade.modules.admin.mapper.MaintainSignSetingMapper;
import org.springblade.modules.admin.service.*;
import org.springblade.modules.admin.vo.MaintainSignSetingVO;
import org.springblade.modules.admin.vo.SignSetingGroupUserVO;
import org.springblade.modules.admin.vo.SignUserVO;
import org.springblade.modules.admin.vo.sign.HolidayVo;
import org.springblade.modules.admin.wrapper.MaintainSignSetingWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 签到规则设置 服务实现类
 *
 * @author zcc
 * @since 2021-07-28
 */
@AllArgsConstructor
@Service
public class MaintainSignSetingServiceImpl extends BaseServiceImpl<MaintainSignSetingMapper, MaintainSignSeting> implements MaintainSignSetingService {

	private final IMaintainSignSetingUserService signSetingUserService;
	private final IMaintainSignCalendarService signCalendarService;
	private final BladeRedis bladeRedis;
	private final IMaintainSignUserHistoryService maintainSignUserHistoryService;
	private final UserSignDetailService userSignDetailService;
	private final IUserWorkforceSignService userWorkforceSignService;

	@Override
	@Transactional(rollbackFor = Exception.class)
	public Boolean saveOrUpdateSignSeting(MaintainSignSetingVO signSeting) {

		if (Func.isNotEmpty(signSeting.getId())) {
			// 删除旧考勤人员
			final MaintainSignSeting seting = this.getById(signSeting.getId());
			final Long setingId = seting.getId();
			// 删除未来日期数据
			final Date date = signSeting.getSignCalendars().get(0).getDate();
			final Integer year1 = Convert.toInt(DateUtil.format(new Date(), "yyyy"));
			final Integer year2 = Convert.toInt(DateUtil.format(date, "yyyy"));
			final Date endOfYear = DateUtil.endOfYear(date);
			Date beginOfYear = null;
			if (year1.equals(year2)) {
				beginOfYear = DateUtil.beginOfDay(new Date());
			} else {
				beginOfYear = DateUtil.beginOfYear(date);
			}
			if (signSeting.getIsUpdate() == 1) {
				signCalendarService.remove(Wrappers.<MaintainSignCalendar>lambdaQuery().eq(MaintainSignCalendar::getSignSetingId, setingId).between(MaintainSignCalendar::getDate, beginOfYear, endOfYear));
			}
			if (!seting.getSignDate().equals(signSeting.getSignDate()) || !seting.getSignOutDate().equals(signSeting.getSignOutDate())) {
				signSeting.setId(null);
			} else {
				signSetingUserService.remove(Wrappers.<MaintainSignSetingUser>lambdaQuery().eq(MaintainSignSetingUser::getSignSetingId, setingId));
			}
		}
		// 更新标记
		boolean updateFlag = Func.isNotEmpty(signSeting.getId());
		try {
			// 时间格式校验 HH:mm:ss
			DateUtil.parseTimeToday(signSeting.getSignDate());
			DateUtil.parseTimeToday(signSeting.getSignOutDate());
		} catch (Exception e) {
			throw new ServiceException("打卡时间格式非法!");
		}

		//如果日历有变动，更新排班信息
		Integer today = Convert.toInt(DateUtil.format(new Date(), "yyyyMMdd"));
		List<UserWorkforceSign> list = userWorkforceSignService.list(Wrappers.<UserWorkforceSign>lambdaQuery().ge(UserWorkforceSign::getSignDate, today));
		if (signSeting.getIsUpdate() == 1) {
			List<Integer> days = list.stream().map(UserWorkforceSign::getSignDate).distinct().collect(Collectors.toList());
			List<MaintainSignCalendar> signCalendars = signSeting.getSignCalendars();
			signCalendars.forEach(o -> o.setSignDate(Convert.toInt(DateUtil.format(o.getDate(), "yyyyMMdd"))));
			Map<Integer, List<MaintainSignCalendar>> listMap = signCalendars.stream().collect(Collectors.groupingBy(MaintainSignCalendar::getSignDate));
			for (Integer day : days) {
				if (listMap.get(day) == null || listMap.get(day).size() == 0) {
					continue;
				}
				MaintainSignCalendar maintainSignCalendar = listMap.get(day).get(0);
				//如果休息日变成了工作日，删除节假日的排班信息
				if (maintainSignCalendar.getHoliday() == 0) {
					userWorkforceSignService.remove(Wrappers.<UserWorkforceSign>lambdaQuery().eq(UserWorkforceSign::getSignDate, day));
				}
			}
		}
		//删除排班不在考勤租的人员
		List<UserWorkforceSign> signs = list.stream().filter(o -> !signSeting.getUserIds().contains(o.getUserId())).collect(Collectors.toList());
		userWorkforceSignService.removeByIds(signs.stream().map(UserWorkforceSign::getId).collect(Collectors.toList()));

		if (this.saveOrUpdate(signSeting)) {
			this.handleSignUser(updateFlag, signSeting.getId(), signSeting.getUserIds());
			if (signSeting.getIsUpdate() == 1) {
				this.handleSignCalendar(updateFlag, signSeting.getId(), signSeting.getSignCalendars());
			}
			//重新添加考勤人员
			int date = Convert.toInt(DateUtil.format(new Date(), "yyyyMMdd"));
			List<UserSignDetail> userSignDetailByDate = this.getUserSignDetailByDate(date);
			for (UserSignDetail userSignDetail : userSignDetailByDate) {
				if (userSignDetail.getHoliday() == 1) {
					userSignDetail.setSignBeLate(2);
					userSignDetail.setSignOutLeaveEarly(2);
				}
				userSignDetailService.save(userSignDetail);
			}
			return Boolean.TRUE;
		}

		return Boolean.FALSE;
	}

	/**
	 * 处理考勤人员
	 *
	 * @param updateFlag   更新标记
	 * @param signSetingId 考勤配置id
	 * @param userIds      用户id集合
	 */
	private void handleSignUser(boolean updateFlag, Long signSetingId, List<Long> userIds) {
		if (Func.isEmpty(userIds)) {
			return;
		}
		// 添加考勤人员
		List<MaintainSignSetingUser> signSetingUserList = new ArrayList<>();
		List<MaintainSignUserHistory> historyList = new ArrayList<>(16);
		final List<Long> longList = userIds.stream().distinct().collect(Collectors.toList());
		final List<MaintainSignSetingUser> list = signSetingUserService.list(Wrappers.<MaintainSignSetingUser>lambdaQuery().eq(MaintainSignSetingUser::getSignSetingId, signSetingId));
		final List<Long> userIds2 = list.stream().map(MaintainSignSetingUser::getUserId).collect(Collectors.toList());
		for (Long userId : longList) {
			if (userIds2.contains(userId)) {
				continue;
			}
			MaintainSignSetingUser signSetingUser = new MaintainSignSetingUser();
			signSetingUser.setSignSetingId(signSetingId);
			signSetingUser.setUserId(userId);
			signSetingUserList.add(signSetingUser);

			MaintainSignUserHistory maintainSignUserHistory = new MaintainSignUserHistory();
			maintainSignUserHistory.setUserId(userId);
			maintainSignUserHistory.setSignSetingId(signSetingId);
			historyList.add(maintainSignUserHistory);
		}
		signSetingUserService.saveBatch(signSetingUserList);
		maintainSignUserHistoryService.saveBatch(historyList);
	}

	/**
	 * 处理考勤日历表
	 *
	 * @param updateFlag       更新标记
	 * @param signSetingId     考勤配置id
	 * @param signCalendarList 日历集合
	 */
	private void handleSignCalendar(boolean updateFlag, Long signSetingId, List<MaintainSignCalendar> signCalendarList) {
		if (Func.isEmpty(signCalendarList)) {
			return;
		}
		// 过滤出未来的日期,并添加
		signCalendarList = signCalendarList.stream().filter(maintainSignCalendar -> DateUtil.compare(maintainSignCalendar.getDate(), DateUtil.parse(DateUtil.today())) >= 0).collect(Collectors.toList());
		if (Func.isNotEmpty(signCalendarList)) {
			signCalendarList.forEach(maintainSignCalendar -> maintainSignCalendar.setSignSetingId(signSetingId));
			signCalendarService.saveBatch(signCalendarList);
		}
	}

	@Override
	public List<MaintainSignSetingVO> detailList(Integer year) {
		BladeUser bladeUser = AuthUtil.getUser();
		List<MaintainSignSetingVO> signSetingList = MaintainSignSetingWrapper.build().listVO(this.list(Wrappers.<MaintainSignSeting>lambdaQuery().eq(MaintainSignSeting::getTenantId, bladeUser.getTenantId()).eq(MaintainSignSeting::getIsDeleted, BladeConstant.DB_NOT_DELETED)));
		signSetingList.forEach(signSetingVO -> {
			// 考勤人员
			List<SignUserVO> userInfos = signSetingUserService.getSignUsers(signSetingVO.getId());
			List<Long> userIds = signSetingUserService.list(Wrappers.<MaintainSignSetingUser>lambdaQuery().eq(MaintainSignSetingUser::getSignSetingId, signSetingVO.getId())).stream().map(t -> t.getUserId()).collect(Collectors.toList());
			signSetingVO.setUserIds(userIds);
			signSetingVO.setUserInfos(userInfos);

			// 考勤日历表
			List<MaintainSignCalendar> calendarList = signCalendarService.getCalendarList(signSetingVO.getId(), year);
			signSetingVO.setSignCalendars(calendarList);
		});
		return signSetingList;
	}

	@Override
	public List<Long> getOtherSignSetingUsers(Long signSetingId) {
		LambdaQueryWrapper<MaintainSignSetingUser> wrapper = Wrappers.lambdaQuery();
		if (Func.isNotEmpty(signSetingId)) {
			wrapper.ne(MaintainSignSetingUser::getSignSetingId, signSetingId);
		}

		BladeUser bladeUser = AuthUtil.getUser();
		// 该租户下所有考勤配置id
		List<Long> signSetingIds = this.list(Wrappers.<MaintainSignSeting>lambdaQuery().eq(MaintainSignSeting::getTenantId, bladeUser.getTenantId())).stream().map(t -> t.getId()).collect(Collectors.toList());
		if (Func.isNotEmpty(signSetingIds)) {
			wrapper.in(MaintainSignSetingUser::getSignSetingId, signSetingIds);
		}
		List<Long> userIds = signSetingUserService.list(wrapper).stream().map(t -> t.getUserId()).collect(Collectors.toList());
		return userIds;
	}

	/**
	 * 免费节假日API
	 * 参考：https://timor.tech/api/holiday
	 * 失败尝试重调3次
	 *
	 * @param year 年份
	 */
	@Override
	public JSONObject getHolidays(Integer year) {
		if (Func.isEmpty(year)) {
			year = DateUtil.year(DateUtil.date());
		}

		// redis获取
		String holidayJson = bladeRedis.get(CacheNames.HOLIDAY_KEY + year);
		if (Func.isNotEmpty(holidayJson)) {
			return JSONUtil.parseObj(holidayJson, false, true);
		} else {
			int count = 0;
			JSONObject jsonObject = null;
			String url = String.format("http://timor.tech/api/holiday/year/%s?week=Y", year);
			do {
				String result = HttpRequest.get(url).execute().body();
				jsonObject = JSONUtil.parseObj(result, false, true);
			} while (Integer.valueOf(jsonObject.get("code").toString()) != 0 && ++count < 3);

			// 若调用成功
			if (Func.isNotEmpty(jsonObject) && Integer.valueOf(jsonObject.get("code").toString()) == 0) {
				bladeRedis.setEx(CacheNames.HOLIDAY_KEY + year, jsonObject.get("holiday").toString(), Duration.ofDays(30));
				return JSONUtil.parseObj(jsonObject.get("holiday"), false, true);
			}

			return null;
		}
	}

	/**
	 * 免费节假日API
	 * 参考：https://timor.tech/api/holiday
	 * 失败尝试重调3次
	 *
	 * @param year 年份
	 */
	@Override
	public JSONObject getHolidaysV2(Integer year) {
		if (Func.isEmpty(year)) {
			year = DateUtil.year(DateUtil.date());
		}

		// redis获取
		String holidayJson = bladeRedis.get(CacheNames.HOLIDAY_KEY + year);
		if (Func.isNotEmpty(holidayJson)) {
			return holidaysTo2024(holidayJson, year);
		} else {
			int count = 0;
			JSONObject jsonObject = null;
			String url = String.format("http://timor.tech/api/holiday/year/%s?week=N", year);
			do {
				String result = HttpRequest.get(url).execute().body();
				jsonObject = JSONUtil.parseObj(result, false, true);
			} while (Integer.valueOf(jsonObject.get("code").toString()) != 0 && ++count < 3);

			// 若调用成功
			if (Func.isNotEmpty(jsonObject) && Integer.valueOf(jsonObject.get("code").toString()) == 0) {
				bladeRedis.setEx(CacheNames.HOLIDAY_KEY + year, jsonObject.get("holiday").toString(), Duration.ofDays(30));
				return holidaysTo2024(jsonObject.get("holiday").toString(), year);
			}

			return null;
		}
	}


	@Override
	public List<UserSignDetail> getUserSignDetailByDate(Integer date) {
		return baseMapper.getUserSignDetailByDate(date);
	}

	@Override
	public Map<Long, List<SignSetingGroupUserVO>> getSignSetingUserInfos() {
		List<SignSetingGroupUserVO> list = baseMapper.getSignSetingUserInfos(AuthUtil.getTenantId());
		return list.stream().collect(Collectors.groupingBy(SignSetingGroupUserVO::getSignSettingId));
	}


	/**
	 * 2024年格式转换
	 *
	 * @param holidayJson
	 * @param year
	 * @return 数据集
	 */
	private JSONObject holidaysTo2024(String holidayJson, Integer year) {
		JSONObject dataList = JSONUtil.parseObj(holidayJson);
		JSONObject jsonObject2024 = new JSONObject();
		jsonObject2024.putOpt("Year", year);
		jsonObject2024.putOpt("isReCreate", true);
		List<HolidayVo> list = new ArrayList<>();
		for (String key : dataList.keySet()) {
			HolidayVo holidayVo = new HolidayVo();
			JSONObject jsonObject = JSONUtil.parseObj(dataList.get(key).toString());
			JSONObject jsonObjectDay = new JSONObject();
			String monthDay = DateUtil.format(DateUtil.parse(jsonObject.get("date").toString(), "yyyy-MM-dd"), "MMdd");
			holidayVo.setMonthDay(monthDay);
			Integer wage = Convert.toInt(jsonObject.get("wage"));
			String name = Convert.toStr(jsonObject.get("name"));
			holidayVo.setRemark(name);
			if (wage == 3 || wage == 2) {
				//等于3表示节假日转换成2023年的格式type=1
				holidayVo.setType("1");
				list.add(holidayVo);
			} else if (wage == 1) {
				//等于1表示节假日补办
				holidayVo.setType("2");
				list.add(holidayVo);
			}
		}
		List<HolidayVo> collect = list.stream().sorted(Comparator.comparing(HolidayVo::getMonthDay)).collect(Collectors.toList());
		jsonObject2024.putOpt("JsonStr", collect);
		return jsonObject2024;
	}
}

